package the.guanzhong.express.service;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.wisteria.framework.util.DateUtils;
import org.wisteria.framework.util.MoneyUtil;
import org.wisteria.framework.web.BusinessException;
import org.wisteria.framework.web.QueryResult;
import the.broccoli.setting.service.IParamsService;
import the.broccoli.weixin.service.WeiXinPayService;
import the.broccoli.weixin.service.dto.WeiXinCreateRefundDTO;
import the.broccoli.weixin.service.dto.WeiXinCreateRefundResultDTO;
import the.guanzhong.express.infrastructure.common.enums.RefundResultEnum;
import the.guanzhong.express.infrastructure.common.enums.RefundStatusEnum;
import the.guanzhong.express.infrastructure.mapper.ExpressOrderMapper;
import the.guanzhong.express.infrastructure.mapper.ExpressRefundMapper;
import the.guanzhong.express.infrastructure.model.GoodsInfoDO;
import the.guanzhong.express.infrastructure.model.OrderDO;
import the.guanzhong.express.infrastructure.model.RefundDO;
import the.guanzhong.express.interfaces.controller.req.*;
import the.guanzhong.express.interfaces.controller.resp.RefundItemResp;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class ExpressRefundService {

    @Resource
    private ExpressRefundMapper expressRefundMapper;

    @Resource
    private ExpressOrderMapper expressOrderMapper;

    @Resource
    private WeiXinPayService weiXinPayService;

    @Resource
    private IParamsService paramsService;

    public List<RefundItemResp> query(RefundQueryReq req) {
        List<RefundItemResp> dataList = expressRefundMapper.selectListQuery(req);
        if (!CollectionUtils.isEmpty(dataList)) {
            dataList.forEach(it -> {
                it.setRefundStatusName(RefundStatusEnum.getName(it.getRefundStatus()));
                it.setRefundResultName(RefundResultEnum.getName(it.getRefundResult()));

                if (StringUtils.hasText(it.getGoodsInfo())) {
                    it.setCargoList(JSON.parseArray(it.getGoodsInfo(), GoodsInfoDO.class));
                }
            });
        }
        return dataList;
    }

    public QueryResult<RefundItemResp> page(RefundPageReq req){
        if (req.getPageNum() != null && req.getPageSize() != null) {
            PageHelper.startPage(req.getPageNum(), req.getPageSize());
        }

        Page<RefundItemResp> page = expressRefundMapper.selectListPage(req);
        if (!CollectionUtils.isEmpty(page.getResult())) {
            page.getResult().forEach(it -> {
                it.setRefundStatusName(RefundStatusEnum.getName(it.getRefundStatus()));
                it.setRefundResultName(RefundResultEnum.getName(it.getRefundResult()));
            });
        }
        return new QueryResult<>(page.getTotal(), page.getResult());
    }

    public boolean apply(RefundApplyReq req) {
        OrderDO order = expressOrderMapper.selectById(req.getOrderId());
        if (order == null) {
            log.info("申请售后退款，订单不存在。orderId: " + req.getOrderId());
            return false;
        }

        RefundDO refund = new RefundDO();
        refund.setCreatedTime(new Date());
        refund.setModifiedTime(new Date());
        refund.setOrderId(req.getOrderId());
        refund.setOutTransactionId(order.getOutTransactionId());
        refund.setRefundFee(order.getPayFee());
        refund.setRefundStatus(RefundStatusEnum.WAITING_AUDIT.getCode());
        refund.setTenantId(order.getTenantId());
        refund.setUserId(order.getUserId());
        refund.setReason(req.getReason());
        refund.setApplyTime(new Date());
        expressRefundMapper.insert(refund);
        return true;
    }

    public boolean audit(RefundAuditReq req) {
        RefundDO refund = expressRefundMapper.selectById(req.getRefundId());
        if (refund == null) {
            log.info("发起退款，退款单不存在。refundId: " + req.getRefundId());
            return false;
        }

        if (req.getAuditResult() == 0) {
            // 拒绝退款
            return expressRefundMapper.update(null, new LambdaUpdateWrapper<RefundDO>()
                    .set(RefundDO::getModifiedTime, new Date())
                    .set(RefundDO::getModifiedId, req.getOperatorId())
                    .set(RefundDO::getRefundStatus, RefundStatusEnum.REJECTED.getCode())
                    .set(RefundDO::getRejectReason, req.getRejectReason())

                    .eq(RefundDO::getId, req.getRefundId())
            ) > 0;
        } else if (req.getAuditResult() == 1) {
            // 审核通过，提起退款
            WeiXinCreateRefundDTO dto = new WeiXinCreateRefundDTO();
            dto.setOrderId(refund.getOrderId());
//            dto.setOutTransactionId(order.getOutTransactionId());
            dto.setRefundId(refund.getId());
            dto.setRefundFee(refund.getRefundFee());
            dto.setNotifyUrl(paramsService.getValue("EXPRESS_REFUND_NOTIFY_URL"));
            WeiXinCreateRefundResultDTO refundResult = weiXinPayService.createRefund(dto);

            if (refundResult == null) {
                return false;
            }

            return expressRefundMapper.update(null, new LambdaUpdateWrapper<RefundDO>()
                    .set(RefundDO::getModifiedTime, new Date())
                    .set(RefundDO::getModifiedId, req.getOperatorId())
                    .set(RefundDO::getRefundStatus, RefundStatusEnum.WAITING_REFUND.getCode())
                    .set(RefundDO::getOutTransactionId, refundResult.getTransactionId())
                    .set(RefundDO::getOutRefundId, refundResult.getOutRefundId())

                    .eq(RefundDO::getId, req.getRefundId())
            ) > 0;
        }
        return false;
    }

    private final ReentrantLock refundLock = new ReentrantLock();
    public boolean completeRefund(WxPayRefundNotifyResult result) {
        WxPayRefundNotifyResult.ReqInfo reqInfo = result.getReqInfo();
        boolean lock = refundLock.tryLock();
        if (!lock) {
            log.info("等待退款执行. OrderCompleteRefundReq:{}", JSON.toJSONString(result));
            throw new BusinessException("等待退款执行");
        }

        try {
            if ("SUCCESS".equals(reqInfo.getRefundStatus())) {
                if (!refundSuccess(reqInfo)) {
                    log.info("更新退款成功结果错误. OrderCompleteRefundReq:{}", JSON.toJSONString(result));
                    throw new BusinessException("更新退款成功结果错误");
                }
            } else if ("CHANGE".equals(reqInfo.getRefundStatus())
                    || "REFUNDCLOSE".equals(reqInfo.getRefundStatus())) {
                if (!refundFail(reqInfo)) {
                    log.info("更新退款失败结果错误. OrderCompleteRefundReq:{}", JSON.toJSONString(result));
                    throw new BusinessException("更新退款失败结果错误");
                }
            } else {
                log.info("退款结果数据错误. OrderCompleteRefundReq:{}", JSON.toJSONString(result));
                throw new BusinessException("退款结果数据错误");
            }
            log.info("执行退款结果完毕. OrderCompleteRefundReq:{}", JSON.toJSONString(result));
        } catch (Exception e) {
            log.error("退款异常.OrderCompleteRefundReq:{}", JSON.toJSONString(result), e);
            throw new BusinessException("退款异常");
        } finally {
            if (refundLock.isLocked() && refundLock.isHeldByCurrentThread()) {
                refundLock.unlock();
            }
        }
        return true;
    }

    private boolean refundSuccess(WxPayRefundNotifyResult.ReqInfo reqInfo) {
        Integer refundId = Integer.parseInt(reqInfo.getOutRefundNo());
        RefundDO refund = expressRefundMapper.selectById(refundId);
        if (RefundStatusEnum.REFUNDED.getCode().equals(refund.getRefundStatus())) {
            return true;
        }

        return expressRefundMapper.update(null, new LambdaUpdateWrapper<RefundDO>()
                .set(RefundDO::getModifiedTime, new Date())
                .set(RefundDO::getRefundStatus, RefundStatusEnum.REFUNDED.getCode())
                .set(RefundDO::getRefundResult, RefundResultEnum.SUCCESS.getCode())
                .set(RefundDO::getActualRefundFee, MoneyUtil.fen2Yuan(reqInfo.getSettlementRefundFee()))
                .set(RefundDO::getSuccessTime, DateUtils.parseTime(reqInfo.getSuccessTime()))

                .eq(RefundDO::getId, refundId)
        ) > 0;
    }

    private boolean refundFail(WxPayRefundNotifyResult.ReqInfo reqInfo) {
        Integer refundId = Integer.parseInt(reqInfo.getOutRefundNo());
        RefundDO refund = expressRefundMapper.selectById(refundId);
        if (RefundResultEnum.FAIL.getCode().equals(refund.getRefundResult())) {
            return true;
        }

        return expressRefundMapper.update(null, new LambdaUpdateWrapper<RefundDO>()
                .set(RefundDO::getModifiedTime, new Date())
                .set(RefundDO::getRefundResult, RefundResultEnum.FAIL.getCode())

                .eq(RefundDO::getId, refundId)
        ) > 0;
    }
}
